open Format

type doc =
  | String of string
  | As of int * string
  | Int of int
  | Float of float
  | Char of char
  | Bool of bool
  | Strings of string list
  | Chars of char list
  | Cut
  | Space
  | Break of int * int
  | HBox of doc list
  | VBox of int * doc list
  | HVBox of int * doc list
  | HOVBox of int * doc list
  | Box of int * doc list
  | List of doc list

let ppf_print ppf doc =
  let is_break doc =
    match doc with
      | Cut | Space | Break (_, _) -> true
      | _ -> false
  in
  let rec f doc =
    match doc with
      | String s -> pp_print_string ppf s
      | As (n, s) -> pp_print_as ppf n s
      | Int i -> pp_print_int ppf i
      | Float f -> pp_print_float ppf f
      | Char c -> pp_print_char ppf c
      | Bool b -> pp_print_bool ppf b
      | Strings ss -> List.iter (pp_print_string ppf) ss
      | Chars cs -> List.iter (pp_print_char ppf) cs
      | Cut -> pp_print_cut ppf ()
      | Space -> pp_print_space ppf ()
      | Break (nspaces, offset) -> pp_print_break ppf nspaces offset
      | HBox docs ->
	  pp_open_hbox ppf ();
	  g docs;
	  pp_close_box ppf ()
      | VBox (ofs, docs) ->
	  pp_open_vbox ppf ofs;
	  g docs;
	  pp_close_box ppf ()
      | HVBox (ofs, docs) ->
	  pp_open_hvbox ppf ofs;
	  g docs;
	  pp_close_box ppf ()
      | HOVBox (ofs, docs) ->
	  pp_open_hovbox ppf ofs;
	  g docs;
	  pp_close_box ppf ()
      | Box (ofs, docs) ->
	  pp_open_box ppf ofs;
	  g docs;
	  pp_close_box ppf ()
      | List docs ->
	  List.iter f docs
  and g docs =
    match docs with
      | [] -> ()
      | d :: [] -> f d
      | d0 :: ((d1 :: _) as rest) when is_break d0 || is_break d1 ->
	  f d0;
	  g rest
      | d :: rest ->
	  f d;
	  pp_print_space ppf ();
	  g rest
  in
    f doc;
    pp_print_flush ppf ()

let print oc doc =
  let ppf = formatter_of_out_channel oc in
    ppf_print ppf doc

let newline oc = Printf.fprintf oc "\n"

let println oc doc =
  print oc doc;
  newline oc
